Flexbox balance: bound memory/CPU via checkpointed readback and a state budget - #1130
Open
nicoburns wants to merge 1 commit into
Open
Flexbox balance: bound memory/CPU via checkpointed readback and a state budget#1130nicoburns wants to merge 1 commit into
nicoburns wants to merge 1 commit into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
Follow-up to #1105 (
flex-wrap: balance). The balancing DP stored a full(line_count - 1) * item_counttable ofu32chosen ends purely for readback, so near-equal author-controlled item/line counts (e.g. 10,000 items withflex-line-count: 9999) could allocate hundreds of MiB and burn seconds of CPU inside layout. This PR bounds both memory and CPU without changing behavior for normal inputs.Changes
Checkpointed readback (exact, same results). The forward pass no longer stores every row's chosen ends. Rows are grouped into blocks of
block_len ≈ sqrt(row_count); the forward pass keeps the score row entering each block (checkpoint) plus the chosen ends of the current block only, and readback recomputes one block at a time from its checkpoint (the final block's ends are still live from the forward pass, so it isn't recomputed). Row computation is factored intoDp::compute_rowso the forward pass and readback recompute rows identically — the objective (sum of squared errors), feasibility constraints, zero-sized-item rule, and largest-end/earliest-line tie-break are all byte-for-byte the same DP; only the storage strategy changed.O(item_count * sqrt(line_count))instead ofO(item_count * line_count)(10,000/9,999 drops from ~381 MiB of ends to ~1 MiB of checkpoints + ends).O(line_count * item_count * log(item_count)).checked_mul.State budget with a deterministic fallback. The exact DP solves
line_count * item_countstates; both factors are author-controlled (flex-line-countis au16, item count unbounded), so the exact solve is limited to a centralized, test-parameterizable budget (EXACT_BUDGET = 1 << 22states, comfortably above anything non-pathological; ~all real content stays exact). Beyond it,proportional_line_item_countsplaces each line break at the feasible position nearest an equal division of the total size inO(item_count log item_count)time andO(item_count)memory: exactly the selected line count, every line within the limit (unless a lone overflowing item), zero-sized items glued to the preceding line where the limit and remaining lines allow. Compatibility tradeoff: over-budget divisions are near-balanced but not the exact squared-error minimum nor the exact tie-break; this only affects inputs that previously took ~seconds/hundreds of MiB.Comment fixes.
collect_balanced_flex_linesandbalanced_line_item_countsclaimed the algorithm makes "the largest line as small as possible"; the implemented (and spec) objective is minimizing the sum of squared errors (equivalently, sum of squared line sizes at fixed line count). Docs now say so, and the asymptotics/memory notes are updated.Tests / benchmarks
matches_naive_dp_many_lines: randomized 80–160-item cases with near-equal line counts (many checkpoint blocks), including zeros/ties straddling recomputed block boundaries, checked against the naiveO(k·n²)oracle.exact_within_budget_matches_unbounded: the default budget produces identical output to an unlimited budget for in-budget inputs.over_budget_fallback_is_valid: randomized inputs forced through the fallback (budget 0) keep exactly the exact algorithm's line count with all-valid lines.pathological_line_counts_stay_bounded: 10,000 items at 5,000 and 9,999 lines complete instantly.flex-wrap: balancecriterion benchmark group inbenches/benches/flexbox.rs:flex-wrap: balance(CSS Flexbox Level 2) #1105)Context
no_std/alloccompatibility preserved (coreonly; verified with--no-default-features --features flexbox,flexbox_balance,alloc),flexbox_balancegate unchanged, MSRV 1.71 respected (nodiv_ceil/is_none_or/f64::sqrt).cargo fmt --all,cargo test --workspace(all green),cargo clippy --workspace -- -D warnings(clean;--all-featureshas a pre-existingstrict_provenanceMSRV lint on main), benches workspace clippy, and the new benchmark. Not run: gentest regeneration (no fixture changes) and--all-featuresclippy-D warningsdue to the pre-existing warning.flex-wrap: balanceis unreleased (Implementflex-wrap: balance(CSS Flexbox Level 2) #1105 post-0.9), so this is a fix to an unreleased feature.Feedback wanted
Whether
EXACT_BUDGET = 1 << 22states is the right threshold — it's centralized and easy to tune.Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/747d1b0632c94205bab965174ba0a777
Requested by: @nicoburns